library(tidyverse) #Data manipulation
library(lubridate) #Date-time handling
library(metacore) #Metadata management
library(sdtm.oak) #SDTM development
library(haven) #File exportClinical Data Journey (Phase 5): Moving Toward Metadata-Driven SDTM Programming using sdtm.oak and metacore
Objective
In the previous article, we explored how metacore metadata objects can be queried and used during SDTM dataset development. We retrieved dataset and variable metadata, compared programmed datasets against specifications, and used metadata as a validation layer. However, metadata-driven programming goes beyond validation. The next step is to integrate metadata with SDTM development workflows, where metadata can guide dataset creation, standardize common derivations, and improve traceability. In this article, we will explore how sdtm.oak, metacore, and metatools can work together to support a more metadata-driven approach for SDTM dataset development.
Load Required Packages
Import raw data
raw_vs <- read_csv("RAW_VS.csv",show_col_types = FALSE)Review the raw data:
head(raw_vs)# A tibble: 6 × 9
STUDYID SITEID Patient_ID VISIT VISITNUM VS_DATE VSTEST VSORRES VSORRESU
<chr> <chr> <chr> <chr> <dbl> <chr> <chr> <dbl> <chr>
1 ONCO-2026 SITE001 SUBJ0001 SCREENI… 1 01-Jan… Systo… 120 mmHg
2 ONCO-2026 SITE001 SUBJ0001 SCREENI… 1 01-Jan… Diast… 80 mmHg
3 ONCO-2026 SITE001 SUBJ0001 SCREENI… 1 01-Jan… Heart… 72 beats/m…
4 ONCO-2026 SITE001 SUBJ0001 VISIT 1 2 15-Jan… Systo… 118 mmHg
5 ONCO-2026 SITE001 SUBJ0001 VISIT 1 2 15-Jan… Diast… 79 mmHg
6 ONCO-2026 SITE001 SUBJ0001 VISIT 1 2 15-Jan… Heart… 70 beats/m…
Load SDTM Metadata
The SDTM specification introduced in previous phases is converted into a metacore object.
spec_file <- system.file(
"extdata",
"SDTM_spec_CDISC_pilot.xlsx",
package = "metacore"
)
meta <- spec_to_metacore(
spec_file
)Retrieve VS Metadata
vs_spec <- select_dataset(
meta,
"VS"
)The metadata object now provides the expected structure of the VS domain.
Retrieve Metadata-Defined Variables
Instead of manually maintaining the SDTM variable list:
vs_vars <- vs_spec$ds_vars$variable
vs_vars [1] "STUDYID" "DOMAIN" "USUBJID" "VSSEQ" "VSTESTCD" "VSTEST"
[7] "VSPOS" "VSORRES" "VSORRESU" "VSSTRESC" "VSSTRESN" "VSSTRESU"
[13] "VSSTAT" "VSLOC" "VSBLFL" "VISITNUM" "VISIT" "VISITDY"
[19] "EPOCH" "VSDTC" "VSDY" "VSTPT" "VSTPTNUM" "VSELTM"
[25] "VSTPTREF"
attr(,"label")
[1] "Variable Name"
The variable structure is obtained directly from metadata. This allows the programming workflow to remain aligned with the specification when metadata changes.
Add OAK Traceability Variables
During SDTM development, maintaining traceability between raw data and SDTM datasets is important. sdtm.oak provides utilities to support standardized traceability.
raw_vs_oak <- generate_oak_id_vars(
raw_dat = raw_vs,
pat_var = "Patient_ID",
raw_src = "RAW_VS"
)Review generated identifiers:
head(
raw_vs_oak %>%
select(
oak_id,
raw_source,
patient_number
)
)# A tibble: 6 × 3
oak_id raw_source patient_number
<int> <chr> <chr>
1 1 RAW_VS SUBJ0001
2 2 RAW_VS SUBJ0001
3 3 RAW_VS SUBJ0001
4 4 RAW_VS SUBJ0001
5 5 RAW_VS SUBJ0001
6 6 RAW_VS SUBJ0001
These variables provide additional lineage information during dataset development.
Derive the SDTM VS Dataset
Metadata defines what the dataset should contain. Programming logic defines how values are derived.
VS <- raw_vs_oak %>%
mutate(
DOMAIN = "VS",
USUBJID = paste(STUDYID, SITEID, Patient_ID, sep = "-"),
VSTESTCD = case_when(
VSTEST == "Systolic Blood Pressure" ~ "SYSBP",
VSTEST == "Diastolic Blood Pressure" ~ "DIABP",
VSTEST == "Heart Rate" ~ "HR",
TRUE ~ NA_character_),
VSDTC = format(dmy(VS_DATE),"%Y-%m-%d")
)Apply Metadata-Based Structure
Use metadata to maintain SDTM variable order:
VS <- VS %>%
select(
any_of(vs_vars)
)The final dataset structure now follows the metadata specification.
Generate SDTM Sequence Variables
SDTM domains with multiple records per subject require sequence variables. sdtm.oak provides reusable utilities for common SDTM derivations. Example:
VS <- VS %>%
arrange(
USUBJID,
VISITNUM,
VSTESTCD
)
VS <- derive_seq(
tgt_dat = VS,
tgt_var = "VSSEQ",
rec_vars = c("VSTESTCD","VSDTC"),
sbj_vars = "USUBJID"
)VSSEQ provides a unique sequence number for each record within a subject, allowing individual observations to be uniquely identified.
Validate Against Metadata
Check expected variables:
setdiff(
vs_vars,
names(VS)
) [1] "VSPOS" "VSSTRESC" "VSSTRESN" "VSSTRESU" "VSSTAT" "VSLOC"
[7] "VSBLFL" "VISITDY" "EPOCH" "VSDY" "VSTPT" "VSTPTNUM"
[13] "VSELTM" "VSTPTREF"
The comparison identifies variables defined in the metadata specification that have not yet been derived in the current SDTM programming step. These variables may require additional derivations or may remain blank depending on study requirements.
Check unexpected variables:
setdiff(
names(VS),
vs_vars
)character(0)
No unexpected variables remain after applying the metadata-defined structure.
Export SDTM Dataset
Clinical submissions commonly require SAS transport files. Using haven:
write_xpt(
VS,
"VS.xpt"
)The SDTM dataset can then be prepared for submission workflows.
Phase 4 vs Phase 5
| Phase 4 | Phase 5 |
|---|---|
| Query metadata | Use metadata during development |
| Validate SDTM structure | Build SDTM workflow |
Explore metacore objects |
Integrate metacore + metatools + sdtm.oak |
| Metadata as reference | Metadata as development framework |
Key Takeaways
In this phase, we moved from metadata-assisted programming toward a metadata-driven workflow. We learned how to:
- Use metacore metadata objects during SDTM development
- Retrieve SDTM structures programmatically
- Use sdtm.oak utilities for traceability and common SDTM tasks
- Maintain alignment between metadata and programmed datasets
- Prepare datasets for downstream submission workflows
In the next phase, we will move from SDTM datasets to ADaM datasets and explore how admiral supports analysis dataset development.